﻿namespace NJQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// A complex type representing a value that is either an unsigned absolute value or a signed relative value from a logical reference point.
	/// </summary>
	[TypeConverter(typeof(RelativeValueConverter))]
	public struct RelativeValue : IEquatable<RelativeValue>, IComplexProperty {


		#region Static Methods

		/// <summary>
		/// Converts a string representation to its <see cref="RelativeValue"/> equivalent.
		/// </summary>
		/// <param name="text">A <see cref="String"/> containing the value to convert.</param>
		/// <param name="result">The parsed value when the conversion succeeds, or a default instance otherwise.</param>
		/// <returns>Indicates whether the conversion succeeded.</returns>
		public static bool TryParse(string text, out RelativeValue result) {

			if(string.IsNullOrEmpty(text)) {
				result = new RelativeValue();
				return false;
			}

			bool relative = (text[0] == '+') || (text[0] == '-');

			int intValue;
			if(int.TryParse(text, out intValue)) {
				result = new RelativeValue(relative, intValue);
				return true;
			}

			result = new RelativeValue();
			return false;
		}

		#endregion


		#region Fields

		private readonly int value;
		private readonly bool relative;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes an instance of the <see cref="RelativeValue"/> type.
		/// </summary>
		/// <param name="relative">If true the value provided is relative to a logical point of reference, otherwise the value provided is absolute and must be unsigned.</param>
		/// <param name="value">The value defined as relative or absolute by the preceding argument.</param>
		/// <exception cref="ArgumentOutOfRangeException">Value cannot be negative unless it is a relative value.</exception>
		public RelativeValue(bool relative, int value) {
			if(!relative && value < 0) throw new ArgumentOutOfRangeException("value", value, "Value cannot be negative unless it is a relative value.");

			this.value = value;
			this.relative = relative;
		}

		#endregion


		#region Properties

		/// <summary>
		/// Returns whether or not the contained value is relative as opposed to absolute.
		/// </summary>
		public bool Relative {
			get { return relative; }
		}

		/// <summary>
		/// Returns the value defined as either relative or absolute.
		/// </summary>
		public int Value {
			get { return value; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current <see cref="RelativeValue"/> object.
		/// </summary>
		/// <param name="obj">The <see cref="Object"/> to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is RelativeValue) return this == (RelativeValue)obj;
			if(obj is int) return this.value == (int)obj && !relative;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified RelativeValue is equal to the current <see cref="RelativeValue"/>.
		/// </summary>
		/// <param name="other">The <see cref="RelativeValue"/> to compare.</param>
		/// <returns>Whether or not the provided value is equal.</returns>
		public bool Equals(RelativeValue other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for <see cref="RelativeValue"/>.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			if(relative) return ~value.GetHashCode();
			return value.GetHashCode();
		}

		/// <summary>
		/// Returns a <see cref="String"/> that represents the current <see cref="RelativeValue"/> object.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {
			if(!relative || value < 0) return value.ToString(CultureInfo.InvariantCulture);
			return "+" + value.ToString(CultureInfo.InvariantCulture);
		}

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client.
		/// </summary>
		/// <returns>The client script value.</returns>
		public string ToClientSource() {
			if(relative) return ToString().ToClientSource();
			return value.ToClientSource();
		}

		#endregion


		#region Operators

		/// <summary>
		/// Allows an integer to be cast automatically as a <see cref="RelativeValue"/> absolute value.
		/// </summary>
		/// <param name="value">The <see cref="Int32"/> value to cast.</param>
		/// <returns>The cast value.</returns>
		/// <exception cref="ArgumentOutOfRangeException">The value must be unsigned.</exception>
		public static explicit operator RelativeValue(int value) {
			return new RelativeValue(false, value);
		}

		/// <summary>
		/// Compares the equality of two <see cref="RelativeValue"/> types.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are equal.</returns>
		public static bool operator ==(RelativeValue value0, RelativeValue value1) {
			return value0.relative == value1.relative && value0.value.Equals(value1.value);
		}

		/// <summary>
		/// Compares the inequality of two <see cref="RelativeValue"/> types.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are distinct.</returns>
		public static bool operator !=(RelativeValue value0, RelativeValue value1) {
			return value0.relative != value1.relative || !value0.value.Equals(value1.value);
		}

		#endregion


	}

}